<?php
/**
 * ActiveRecord
 *
 * Класс ActiveRecord является базовым классом для всех объектов БД
 *
 * @author Кузьма Дмитрий <kouzma.d@gmail.com>
 * @version 1.1
 */
class ActiveRecord {
//ВАЖНЫЕ настройки сложных выборок/поисков:
//combine - многомерная выборка. Используется для поиска all //'combine' => array('sport' => 'ID_sport, sport_name', 'user' => 'login, ID_user')
	protected $tableName; 							// название таблицы в БД
	protected $pkName;									// название первичного ключа в БД
	public    $tableFields = array(); 	//Массив загруженных в объект полей
	public    $id = null;              	// первичный ключ объекта
	private   $loaded = false; 					//флаг загрузки объекта из БД
	protected $arrSettings;							//массив дополнительных свойств объекта
	protected $arrSaveInclude = array();//позволяет сохранять каскадно. Например амплуа для видов спорта array('PlayerRole')
	protected $arrSaveIncludeSettings = array(); // настройки для массива $arrSaveInclude
	protected $essenceType = 0;					//тип сущности, к которому относится объект
	protected $tableDeleteFlag = 'is_deleted';					// флаг псведоудаления в бд

  protected static $suggestLimit = 20; //ограничение на количество подсказываемых элементов

	private $stmt;

	private $select = '*';							//Образующийся сковый запрос
	private $conditions = null;					//Условия поиска (то, что пишется после WHERE)
	private $order = null;							//Условия сортировки - то, что пишется после ORDER BY
	private $group = null;							//Условия группировки - то, что пишется после GROUP BY
	private $limit = null;							//Ограничение на получение элементов в поиске
	private $offset = 0;								//Позиция, начиная с которой следует выдавать поисковый результат
	private $include = array();					//Массив. Служит для добавления JOIN`ов к запросу
	private $includeList = array();			//Массив. Позволяет добавить список элементов для загрузки из БД. Например, амплуа для видов спорта array('player_role'). Используется для поиска одного элемента (можно прямо в конструкторе)
	private $combine = array();					//Массив. Позволяет настроить выборку так, что будут подключены доп. таблицы, а результат будет возвращен в виде многомерного маасива
																			//Имеет вид ('таблица' => 'поле1[, поле2]'[, 'таблица' => 'поле1[, поле2]'])
																			//'combine' => array('sport' => 'ID_sport, sport_name', 'user' => 'login, ID_user')
	private $joins = null;							//Строка. Позволяет вручную вставить подстроку JOIN ...
	private $getAuthor; 								//Для автоматического получения данных об авторе
	private $combineConverted = array();//Многомерный массив. Представляет собой массив $combine, в котором строки представлены массивами

	  protected $validatesAcceptanceOf;   // проверка установки флажка
	  protected $validatesAssociated;     // проверка связанных объектов
	  protected $validatesConfirmationOf; // проверяет совпадение с полем двойником
	protected $validatesExclusionOf;    // проверяет остутсвие значения свойств в наборе
	  protected $validatesFormatOf;       // проверяет на соответствие регулярному выражению
	protected $validatesInclusionOf;    // проверяет значения свойств на присутствие в наборе
	  protected $validatesLengthOf;       // проверяет длину значения свойства
	  protected $validatesNumericalityOf; // проверяет что свойство содержит правильное число
	  protected $validatesPresenceOf;     // Проверяет, что свойство не имеет пустого значения
	  protected $validatesUniquenessOf;   // Проверяет свойство на уникальность


	protected $arrHasMany = array(); // Массив атрибутов, которые являются объектами класса HasMany;
	protected $arrBelongsTo = array(); // Массив атрибутов, которые являются объектами класса HasMany;

	protected $arrWysiwyg = array();


  public function __construct($arrInitialFields = null, $arrSettings = array()) {
    if ($arrSettings) foreach ($arrSettings as $settingName => $settingValue) {
  		$this->setSetting($settingName, $settingValue);
  	}
  	if (is_numeric($arrInitialFields)) {
  	  // Осуществляем поиск и сохранение результатов
  	  $this->_findOne($arrInitialFields);
  		$this->id = $this->tableFields[$this->pkName];
		}
		else {
  	  $this->tableFields = &$arrInitialFields;
  	  // Назначение PK
  	  if (isset($this->tableFields[$this->pkName])) {
		  	$this->id = $this->tableFields[$this->pkName];
  	  	//unset($this->tableFields[$this->pkName]);
  	  }
  	  // Убирание лишних символов WYSIWYG
  	  if ($this->arrWysiwyg) {
  	    foreach ($this->arrWysiwyg as $wysiwygField) {
  	    	if (isset($this->tableFields[$wysiwygField])) $this->tableFields[$wysiwygField] = stripcslashes($this->tableFields[$wysiwygField]);
  	    }
  	  }
		}
  }
  /**
   * Метод устанавливает значение поля
   *
   * @param string $fieldName - название поля
   * @param mixed $fieldValue - значение поля
   */
  public function setField($fieldName, $fieldValue) {
  	$this->tableFields[$fieldName] = $fieldValue;
  }
  /**
   * Метод устанавливает значение всех полей объекта, согласно переданному массиву
   *
   * @param array $tableFields
   */
  public function setFields($tableFields, $reload = false) {
  	if ($reload)
  	  $this->tableFields = &$tableFields;
  	else
  	  foreach ($tableFields as $key => $value)
  	  	$this->tableFields[$key] = $value;
  }
  /**
   * Метод возвращает значение поля
   *
   * @param string $fieldName
   * @return mixed
   */
  public function getField($fieldName) {
  	if (isset($this->tableFields[$fieldName])) return $this->tableFields[$fieldName];
  	else return null;
  }
  /**
   * Метод проверяет список элементов класса
   *
   * @param array $arrList
   */
  public function validateList($arrList, $objName) {
  	foreach ($arrList as $i => $element) { // Проходим по всем элементамсписка

  	  // Создаем новый объект для элемента
  		$s = "\$obj = new " . $objName . "(\$element);";
  		eval($s);

  		$obj->tableFields[$this->pkName] = $this->id;
			if ($obj->getField($obj->pkName)) {
			  $obj->id = $obj->getField($obj->pkName);
			  $arrIdsList[] = $obj->id;
			}
			if (!isset($this->arrSaveIncludeSettings[$objName]['onlyThrow']) || !$this->arrSaveIncludeSettings[$objName]['onlyThrow']) // если мы сохраняем не только промежуточную таблицу
			  $obj->fullValidation(array('errorOf' => array(get_class($this), $objName), 'complex' => $i, 'rowException' => true));
			elseif (isset($this->arrSaveIncludeSettings[$objName]['required']) && $this->arrSaveIncludeSettings[$objName]['required']) { // Если что-то в списке обязательно должно быть
				if ($this->arrHasMany) foreach ($this->arrHasMany as $hasMany) {
		  		if ($hasMany->tableName != $obj->tableName) $hasMany = null;
		  		else break;
		  	}
				$idSet = false;
		  	if ($arrList && $hasMany) foreach (Service_ArrayConverter::getArrayColumn($arrList, $hasMany->fkName) as $currentId) {
		  		if ($currentId) {
		  			$idSet = true;
						break;
					}
				}
				if (!$idSet) {
				  $arrSettings = array('errorOf' => array(get_class($this), $objName), 'complex' => $i, 'rowException' => true);
					$obj->errorValidated($objName, $arrSettings, $this->arrSaveIncludeSettings[$objName]['required']);
				}
			}
  	}
  }
  
  /**
   * Метод проверяет вложенный объект
   *
   * @param array $arrList
   */
  public function validateInnerObject($arrFields, $objName) {
	  // Создаем новый объект для элемента
		
	  $s = "\$obj = new " . $objName . "(\$arrFields);";
		eval($s);

		$obj->tableFields[$this->pkName] = $this->id;
		if ($obj->getField($obj->pkName)) {
		  $obj->id = $obj->getField($obj->pkName);
		}
		$obj->fullValidation(array('errorOf' => array(get_class($this), $objName)));
		
		/*
		if (!isset($this->arrSaveIncludeSettings[$objName]['onlyThrow']) || !$this->arrSaveIncludeSettings[$objName]['onlyThrow']) // если мы сохраняем не только промежуточную таблицу
		  $obj->fullValidation(array('errorOf' => array(get_class($this), $objName), 'complex' => $i, 'rowException' => true));
		elseif (isset($this->arrSaveIncludeSettings[$objName]['required']) && $this->arrSaveIncludeSettings[$objName]['required']) { // Если что-то в списке обязательно должно быть
			if ($this->arrHasMany) foreach ($this->arrHasMany as $hasMany) {
	  		if ($hasMany->tableName != $obj->tableName) $hasMany = null;
	  		else break;
	  	}
			$idSet = false;
	  	if ($arrList && $hasMany) foreach (Service_ArrayConverter::getArrayColumn($arrList, $hasMany->fkName) as $currentId) {
	  		if ($currentId) {
	  			$idSet = true;
					break;
				}
			}
			if (!$idSet) {
			  $arrSettings = array('errorOf' => array(get_class($this), $objName), 'complex' => $i, 'rowException' => true);
				$obj->errorValidated($objName, $arrSettings, $this->arrSaveIncludeSettings[$objName]['required']);
			}
		}*/
  }
  /**
   * Метод сохраняет список элементов класса
   *
   * @param array $arrList
   */
  private function saveList($arrList, $objName) {
    //echo 'Save list';
    $objDB = Service_Database::instance();//Получаем подключение к БД
  	$s = "\$obj = new " . $objName . "();";
  	eval($s);
  	if ($this->arrHasMany) foreach ($this->arrHasMany as $hasMany) {
  		if ($hasMany->tableName != $obj->tableName) $hasMany = null;
  		else break;
  	}
  	// Получаем список айдишников связи
  	if (!$hasMany || !$hasMany->throw) {
  		$stmt = "SELECT `" . $obj->pkName . "` FROM `" . $obj->tableName . "` WHERE `" . $this->pkName . "` = '" . $this->id . "'";
  	}else {
  		$stmt = "SELECT `" . $hasMany->fkName . "` FROM `" . $hasMany->throw . "` WHERE `" . $this->pkName . "` = '" . $this->id . "'";
  	}
  	
  	$arrExistIdsList = $objDB->selectSimpleArray($stmt);

  	$i = 0;

  	//Сохраняем новые/редактируем старые
  	if (!isset($this->arrSaveIncludeSettings[$objName]['onlyThrow']) || !$this->arrSaveIncludeSettings[$objName]['onlyThrow']) { // обычное сохранение
  		//echo 'Only throw';
  	  $arrIdsList = array();
  		foreach ($arrList as $element) {
	  		//echo 'Element';
  		  //$obj = new PlayerRole($element);
	  		$s = "\$obj = new " . $objName . "(\$element);";
	  		eval($s);
	  		$obj->tableFields[$this->pkName] = $this->id;
				if ($obj->getField($obj->pkName)) {
				  $obj->id = $obj->getField($obj->pkName);
				  $arrIdsList[] = $obj->id;
				  $obj->save();
				}
				else {
				  $obj->save();
					if ($hasMany && $hasMany->throw) { // добавлеяем промежуточную связь
					  $stmt = "INSERT INTO `" . $hasMany->throw . "` (`" . $this->pkName . "`, `" . $hasMany->fkName . "`) VALUES ('" . $this->id . "', '" . $obj->id . "');";
					  $objDB->query($stmt);
					}
				}
				$i++;
	  	}
  	}
  	else { // сохранение промежуточной таблицы
  		//echo 'Insert';
  	  $arrIdsList = Service_ArrayConverter::array_iunique(Service_ArrayConverter::getArrayColumn($arrList, $hasMany->fkName));
			$arrNewIdsList = Service_ArrayConverter::arrayFullDifference($arrIdsList, $arrExistIdsList);
			foreach ($arrNewIdsList as $currentId) {
				if ($currentId) {
					$stmt = "INSERT INTO `" . $hasMany->throw . "` (`" . $this->pkName . "`, `" . $hasMany->fkName . "`) VALUES ('" . $this->id . "', '" . $currentId . "');";
  				$objDB->query($stmt);
				}
			}
  	}
  	$arrDeleteIdsList = Service_ArrayConverter::arrayFullDifference($arrExistIdsList, $arrIdsList);
  	if ($arrDeleteIdsList) {
  	  if ($hasMany && $hasMany->throw) {
  	    $stmt = "DELETE FROM  `" . $hasMany->throw . "` WHERE `" . $this->pkName . "` = '" . intval($this->id) . "' AND `" . $hasMany->fkName . "` IN (" . Service_Function::intEscapeJoin(', ', $arrDeleteIdsList) . ")";
  	    $objDB->query($stmt);
  	  }
  	  else $obj->delete_all("`" . $obj->pkName . "` IN (" . Service_Function::intEscapeJoin(', ', $arrDeleteIdsList) . ")");
  	}
  }

  /**
   * Метод полной проверки всех значений класса. Вызывает все необходимые подметоды проверки. beforeValidation и прочие.
   *
   * @param array $arrAdditionalSettings
   */
  public function fullValidation($arrAdditionalSettings = array()) {
    $this->beforeValidation($arrAdditionalSettings);
		if ($this->id) $this->beforeValidationOnUpdate();
  	else $this->beforeValidationOnCreate();
		$this->validation('save', $arrAdditionalSettings);
		if (!$this->id) $this->validation('create', $arrAdditionalSettings);
		else $this->validation('update', $arrAdditionalSettings);
		$this->afterValidation();
		if ($this->id) $this->afterValidationOnUpdate();
		else $this->afterValidationOnCreate();
  }

  /**
   * Метод производит сохранение объекта
   *
   * @return array
   */
  public function save() {
  	// Устанавливаем флаг. Происходит добавление или редактирование.
    if (!$this->id) $isAdding = true;
  	else $isAdding = false;
  	// Осуществляем проверку данных перед сохранением
  	$this->fullValidation();
		//валидируем каскадные вещи
  	foreach ($this->tableFields as $key => $value) {
			if (Service_ArrayConverter::in_iarray($key, $this->arrSaveInclude)) {
					$this->validateList($value, $key);
   	  }
  	}
  	// Если валидация прошла успешно, то выполняем непосредственно сохранение вместе с сопутствующими методами вроде beforeSave
  	if (!Exception_ActiveRecord::isExceptionOfModelExists(get_class($this))) {
  		$this->beforeSave();
  		if (!$this->id) $this->beforeCreate();
			else $this->beforeUpdate();
			//echo '8';
			$this->_save();
			//echo '9';
    	if ($isAdding) {
    	  //echo '8';
    	  $this->afterCreate();
    	  //echo '7';
    	}
    	else $this->afterUpdate();
    	//echo '8';
  		$this->afterSave();
  		//echo '7';
  	}
  	else return false;

  	// Возвращаем идентификатор сохраненной сущности
		return $this->id;
  }

  /**
   * Метод производит непосредственное сохранение объекта
   *
   * @return array
   */
  protected function _save() {
  	//echo '9';
    // Если необходимо сохранять изображение (аватарку)
    if (isset($this->arrSettings['image'])) {
      if (isset($this->image) && $this->image->isLoaded()) {
				if ($this->id) { //удаляем едыдущие картинки
					$s = "\$objThis = new " . get_class($this) . "(" . $this->id . ");";
					eval($s);
					Image::deleteAvatarOfElement($objThis->getField('pic_folder'), $objThis->getField('pic_file'));
				}
				$this->image->save();
				$this->tableFields = array_merge($this->tableFields, $this->image->dbGetArrToSave());
			}
    }
    //echo '8';
  	// Если необходимо сохранять изображение (иконку)
    if (isset($this->arrSettings['ico'])) {
      if (isset($this->ico) && $this->ico->isLoaded()) {
				if ($this->id) { //удаляем едыдущие картинки
					$s = "\$objThis = new " . get_class($this) . "(" . $this->id . ");";
					eval($s);
					Image::deleteAvatarOfElement($objThis->getField('ico_folder'), $objThis->getField('ico_file'));
				}
				$this->ico->save('ico_folder', 'ico_file');
				$this->tableFields = array_merge($this->tableFields, $this->ico->dbGetArrToSave('ico_folder', 'ico_file', false));
			}
		}
    //echo '7';
		// Сохраняем непосредственно сам объект
		if (!$this->id) { $this->add();}
  	else {
  	  //echo '0';
  	  $this->edit();
  	  //echo '1';
    }
    //echo '6';
  	//Сохраняем каскадные вещи
  	foreach ($this->tableFields as $key => $value) {
			if (Service_ArrayConverter::in_iarray($key, $this->arrSaveInclude)) {
			  $this->saveList($value, $key);
   	  }
  	}
  	//echo '5';
  }
  /**
   * Метод добавляет объект в БД
   *
   * @return integer
   */
  protected function add() {
		$objDB = Service_Database::instance();//Получаем подключение к БД

		// Устанавливаем поля created_on / created_at, если нужно
		if (isset($this->arrSettings['created_at'])) $this->tableFields['created_at'] = Service_Date::gLocalTime2BD();
		if (isset($this->arrSettings['created_on'])) $this->tableFields['created_on'] = Service_Date::gLocalTime2BD();

		// Если необходимо сохранять теги
		if (isset($this->arrSettings['tags'])) {
			// Если теги заданы
		  if (!empty($this->tableFields['str_tags'])) {
			  //Формируем массив тегов
		    $arrTags = explode(',', str_replace(',,', ',', trim($this->tableFields['str_tags'], ', ')));
			  //Убираем в массиве тегов лишние символы
		    for ($i = 0; $i < sizeof($arrTags); $i++) $arrTags[$i] = trim($arrTags[$i]);
			  //Убираем повторяющиеся теги
		    $arrTags = array_unique($arrTags);
			  //Формируем новую строку тегов
		    $this->tableFields['str_tags'] = implode(', ', $arrTags);
			}
		}

		// Если необходимо сохранять автора
		if (isset($this->arrSettings['author'])) {
			$objSession = User_Session::instance();
			if ($objSession->getUserId()) {
				if ($this->arrSettings['author'] === true)
					$this->tableFields['ID_user'] = $objSession->getUserId();
				else $this->tableFields[$this->arrSettings['author']] = $objSession->getUserId();
			}
		}
		//Добавлем элемент в БД
		$this->id = $objDB->insertAutoincrement($this->tableName, $this->_getArrToSave());
		// Если необходимо сохранять теги
		if (isset($this->arrSettings['tags'])) {

		  // Если у нас есть теги
		  if (!empty($this->tableFields['str_tags'])) {
			  /*
		    $objTags = new Tags_Tags();
			  $objTags->editTags($this->id, Service_Function::absolutelyEscapeString($this->tableFields['str_tags']), $this->tableName);
			  */
			  $objTagEssence = new Tag_Essence($this->id, $this->essenceType);
		    $objTagEssence->saveForEssence($this->tableFields['str_tags']);
			}

		}
		//echo $this->id;
		return $this->id;
  }

  /**
   * Метод обновляет объект в БД
   *
   */
  protected function edit() {
  	//echo '0';
    $objDB = Service_Database::instance();//Получаем подключение к БД

  	// Устанавливаем поля updated_on / updated_at, если нужно
		if (isset($this->arrSettings['updated_at']))	$this->tableFields['updated_at'] = Service_Date::gLocalTime2BD();
		if (isset($this->arrSettings['updated_on'])) $this->tableFields['updated_on'] = Service_Date::gLocalTime2BD();

		//echo '1';
		// Если нужно сохранять теги
		if (isset($this->arrSettings['tags'])) {
			// Если имеется соответствующие поле для сохранения
		  if (isset($this->tableFields['str_tags'])) {
			  /*
		    $objTags = new Tags_Tags();
			  // Сохраняем теги в отдельные таблицы для тегов
			  $arrTags = $objTags->editTags($this->id, Service_Function::absolutelyEscapeString($this->tableFields['str_tags']));
			  // Формируем массив тегов
			  $arrTags = explode(',', str_replace(',,', ',', trim($this->tableFields['str_tags'], ', ')));
			  // Убираем для массива тегов лишние символы
			  for ($i = 0; $i < sizeof($arrTags); $i++) $arrTags[$i] = trim($arrTags[$i]);
			  // Убираем повторяющиеся теги
			  $arrTags = array_unique($arrTags);
			  // Формируем новую строку тегов
			  $this->tableFields['str_tags'] = implode(', ', $arrTags);
			  */
			  $objTagEssence = new Tag_Essence($this->id, $this->essenceType);
			  $objTagEssence->saveForEssence($this->tableFields['str_tags']);
			  $this->tableFields['str_tags'] = $objTagEssence->getLinear(', ');
			}
		}
		//echo '2';

		//$arrRet = $objDB->update($this->tableName, $this->_getArrToSave(), array($this->pkName => $this->id));
		//return $arrRet;
		//echo '3';
		return $objDB->update($this->tableName, $this->_getArrToSave(), array($this->pkName => $this->id));
  }

  /**
   * Удаляет из массива лишние элементы для сохранения (например, _confirmation)
   *
   * @return array
   */
  private function _getArrToSave() {
  	$sizeFields = sizeof($this->tableFields);
  	$arrReturn = $this->tableFields;

  	// Убираем из сохраняемого массива то, что не относится непосредственно к сохраняемому
  	unset($arrReturn['save']);
  	unset($arrReturn['save_x']);
  	unset($arrReturn['save_y']);
  	unset($arrReturn['add']);
  	unset($arrReturn['add_x']);
  	unset($arrReturn['add_y']);
  	unset($arrReturn['edit']);
  	unset($arrReturn['edit_x']);
  	unset($arrReturn['edit_y']);

  	// Удаляем из массива чекбоксы вроде "согласен с правилами использования"
  	if ($this->validatesAcceptanceOf) foreach ($this->validatesAcceptanceOf as $keyToUnset => $value) {
			unset($arrReturn[$keyToUnset]);
		}


		// обработка полей
		foreach ($arrReturn as $key => $value) {
			if (is_array($value)) unset($arrReturn[$key]); // Убираем из сохранения массивы
			else {
				// Убираем из сохранения поля, оканчивающиеся на confirm
				$confirmPos = strpos($key, '_confirm');
			  if ($confirmPos) {
			  	if (isset( $this->validatesConfirmationOf[substr($key, 0, $confirmPos)] )) unset($arrReturn[$key]);
			  }
			  // Экранируем все вредоносные символы
			  if (!is_numeric($value) && $value !== null) $arrReturn[$key] = Service_Function::absolutelyEscapeString($value);
			}
  	}

    // Делаем отдельную обработку на вредоносный код у полей WYSIWYG
  	foreach ($this->arrWysiwyg as $key => $value) {
  		if (isset($arrReturn[$value]))
  		  $arrReturn[$value] = Service_Function::absolutelyEscapeStringFromWysiwyg($this->tableFields[$value]);
  	}

		// возвращаем сохраняемый массив значений
  	if (isset($arrReturn)) return $arrReturn;
  	else return $this->tableFields;
  }


  /**
   * Метод находит в БД объект/объекты
   *
   * @param string $itemsToFind - тип поиска - число (id)/all/first
   * @param array $arrSettings - свойства иска
   * @return array
   */
  public function find($itemsToFind, $arrSettings = array()) {
		// устанавливаем умолчания на случай, если через этот объект уже осуществляли поиск
    $this->limit = null;
		$this->offset = 0;
		$this->conditions = null;

		// Устанавливаем все поисковые свойства
		foreach ($arrSettings as $settingName => $settingValue) {
			$this->setSetting($settingName, $settingValue);
  	}

  	//определяем тип поиска и делаем соответствующий вызов
		if (is_integer($itemsToFind))
		  return $this->_findOne($itemsToFind);
		elseif ($itemsToFind == 'all')
			return $this->_findAll();
		elseif ($itemsToFind == 'first')
		  return $this->_findFirst();
		else
		  throw new Exception('The first parameter to the find method should be either an identifier or "all", or "first". You gave "' . $itemsToFind . '"');

	}

	/**
	 * Метод возвращает данные указанной таблицы
	 *
	 * @param string $tableName
	 * @param array $arrSettings
	 * @return mixed
	 */
	public function findInTabe($tableName, $arrSettings = array()) {
		// устанавливаем поисковые свойства
	  foreach ($arrSettings as $settingName => $settingValue) {
  		$this->setSetting($settingName, $settingValue);
  	}

		$objDB = Service_Database::instance();// Получаем подключение к БД
		$this->stmt = "SELECT " . $this->select .
						" FROM `" . $tableName . "`" .
						($this->conditions ? " WHERE " . $this->conditions : null) .
						($this->group ? " GROUP BY " . $this->group : null) .
						($this->order ? " ORDER BY " . $this->order : null) .
	        	(isset($this->limit) ? " LIMIT " . intval($this->offset) . ", " . intval($this->limit) : null);
		return $objDB->select($this->stmt);	// Получаем данные об основной сущности
	}

	/**
	 * Метод поиска одного элемента
	 *
	 * @param integer $idToFind - id для поиска
	 * @return array
	 */
	private function _findOne($idToFind) {
	  $objDB = Service_Database::instance();// Получаем подключение к БД

	  $this->conditions = "`" . $this->pkName . "` = '" . $idToFind . "'"; // настраиваем поиск по айдишнику
		// устанавливаем умолчания на случай, если через этот объект уже осуществляли поиск
	  $this->limit = 1;
	  $this->offset = 0;

	  // осуществляем поиск и сохранение результатов
	  $this->_makeStmt();
	  $this->tableFields = $objDB->selectOne($this->stmt);
	  $this->id = isset($this->tableFields[$this->pkName]) ? $this->tableFields[$this->pkName] : null;
	  $this->loaded = true;


	  // Если нужно добавить список к результату
	  if ($this->includeList) {
	    // Проходимся по всем таблицам из тех, что нужно присоединить
	    foreach ($this->includeList as $joinTableName) {
				// Проходимся по всем атрибутам текущего класса, чтобы найти атрибуты, соответствующие классу HasMany
	      foreach (get_object_vars($this) as $attrName => $attrValue) {
					// Если мы нашли атрибут класса HasMany
	        if (is_a($this->$attrName, 'HasMany')) {
				  	$objHasMany = $this->$attrName; // Считываем объект класса HasMany
				  	if ($objHasMany->tableName == $joinTableName) { // Проверяем, отвечает соответствующий класс за ту же таблицу или нет
				  		// Считываем результаты для смежной таблицы и записываем результаты в соответствующее поле
				  	  $stmt = "SELECT * FROM `" . $objHasMany->tableName . "` WHERE `" . $this->pkName . "` = '" . $this->id . "'";
				  		$this->tableFields[$objHasMany->className] = $objDB->select($stmt);
				  	}
					}
				}
			}
	  }
	  // Возвращаем сформированный результат
	  return $this->tableFields;
	}

	/**
	 * Метод поиска первого элемента в списке
	 *
	 * @return array
	 */
	private function _findFirst() {
	  $objDB = Service_Database::instance();// Получаем подключение к БД
	  // устанавливаем умолчания на случай, если через этот объект уже осуществляли поиск
		$this->limit = 1;
	  $this->offset = 0;

	  // осуществляем поиск и сохранение результатов
	  $this->_makeStmt();
	  $this->tableFields = $objDB->selectOne($this->stmt);
	  $this->id = $this->getField($this->pkName);
	  $this->loaded = true;

	  // Возвращаем сформированный результат
	  return $this->tableFields;
	}

	/**
	 * Переводим массив подключаемых полей, к многомерному виду
	 *
	 */
	private function convertCombineToAbsArr() {
		// проходим по всему массиву $this->combine (т.е. проходим по подключаемым таблицам)
		foreach ($this->combine as $combineName => $combine) {
			$arrCombine = explode(',', $combine); // Получаем набор полей для каждой из подключаемых таблиц
			// Проходим по всем полям в каждой подключаемой таблице
			foreach ($arrCombine as $fieldName) {
		    $fieldName  = trim($fieldName, ' `');
		    $this->combineConverted[$combineName][] = $fieldName; // Записываем в многомерный массив-комбайн/ Будет выглядеть как combineConverted['connected_table']['field']
			}
		}
	}
	/**
	 * Метод поиска списка элементов
	 *
	 * @return array
	 */
	private function _findAll() {
		$objDB = Service_Database::instance();// Получаем подключение к БД
		$this->_makeStmt();
		//echo $this->stmt;
		if (!$this->combine) { // Если обычная выборка
			return $objDB->select($this->stmt);
	  }
	  else { // Если комбинированная выборка, т.е. с выборкой из связных таблиц
	  	$arr = $objDB->select($this->stmt);	// Получаем данные об основной сущности вместе с данными о сопутствующих элементах
	  	$this->convertCombineToAbsArr(); 		// Переводим настройки комбайна в двумерный массив
	  	$i = 0;
	  	$arrRet = array();
	  	$arrLink = array();
	  	$pkName = $this->pkName;

	  	// Проходим по всем элементам основной сущности
	  	if ($arr) foreach ($arr as $row) {
	  		$id = $row[$pkName];
	  		if (!isset($arrLink[$id])) { // Если элемент встречается впервые
	  		  //Проходим по всем подключаемым таблицам (combineConverted['connected_table']['field'])
	  			foreach ($this->combineConverted as $combineName => $arrCombine) {
	  				if ($combineName != $this->tableName) {//Если это именно подключаемая таблица, а не собственная, тогда сохраняем все поля, относящиеся к этой таблице в отдельный массив
		  		  	//Проходим по всем полям подключаемой таблице
	  					foreach ($arrCombine as $fieldName) $arrAdditional[$fieldName] = $row[$fieldName]; //Формируем подмассив для сохранения
		  		  	$arrRet[$i][$combineName][] = $arrAdditional;  //Сохраняем подмассив
	  		  	}
	  		  	else{ // Если это собственная таблица, то прото переписываем все поля, относящиеся к этой таблице
	  		  		foreach ($arrCombine as $fieldName)	$arrRet[$i][$fieldName] = $row[$fieldName];
	  		  	}
	  		  }
	  		  $arrLink[$id] = $i; // Записываем ссылку на место, где сохранен соответствующий айдишник
	  		  $i++;
	  		}
	  		else { // Если элемент уже встречался
  			  $iTo = $arrLink[$id]; // Определяем, куда нужно сохранить элемент
	  			//Проходим по всем подключаемым таблицам
  			  foreach ($this->combineConverted as $combineName => $arrCombine) {
	  				if ($combineName != $this->tableName) {//Если это именно подключаемая таблица, а не собственная
		  		  	//Проходим по всем полям подключаемой таблице
		  		  	foreach ($arrCombine as $fieldName)	$arrAdditional[$fieldName] = $row[$fieldName]; //Формируем подмассив для сохранения
		  		  	$arrRet[$iTo][$combineName][] = $arrAdditional;  //Сохраняем подмассив
	  		  	}
	  			}
	  		}
	  	}
	  	return 	$arrRet;
	  }
	}

	/**
	 * Метод получения коллекции элементов
	 *
	 * @param string $collectionName
	 * @param array $arrSettings
	 * @return array
	 */
	public function getCollection($collectionName, $arrSettings = array()) {
	  if (property_exists($this, $collectionName)) { // Проверяем, есть ли в классе такой атрибут (укзывающий на коллекцию)
			if (is_a($this->$collectionName, 'HasMany')) { // Убеждаемся, что атрибут указывает именно на коллекцию
				// Формируем объект класса, к типу которого относятся элементы коллекции
			  $objHasMany = $this->$collectionName;
			  $s = "\$objRet = new " . $objHasMany->className . "();";
			  eval($s);

			  // Если условия поиска объекта не заданы явным образом, то поиск и присоединяемые таблицы формируются автоматически
			  if (!isset($arrSettings['conditions'])) {
			  	$arrSettings['conditions'] = $this->pkName . '=' . $this->id;
			    if (isset($objHasMany->throw)) {
			    	$arrSettings['joins'] = " LEFT JOIN `" . $objHasMany->throw . "` ON `" . $objHasMany->throw . "`.`" . $objRet->pkName . "` = `" . $objRet->tableName . "`.`" . $objRet->pkName . "` ";
			    }
			  }
				return $objRet->find('all', $arrSettings);
			}
		}
		else return false;

	}

	/**
	 * Метод формирования поисковой строки
	 *
	 */
	private function _makeStmt() {
		$this->stmt = "SELECT " . (($this->combine && ($this->select == '*')) ? $this->_makeCombineSelect() : ($this->select == '*' ? '`' . $this->tableName . '`.*' : $this->select )) .
						($this->getAuthor ? ", `user`.`ID_user`, `is_admin`, `user_role`, `first_name`, `last_name`, `middle_name`, `login`, `user`.`pic_folder`, `user`.`pic_file`" : null) .
		        " FROM `" . $this->tableName . "`" .
		        ($this->joins ? (" " . $this->joins) : null) .
		        ($this->getAuthor ? " LEFT JOIN `user` ON `user`.`ID_user` = `" . $this->tableName . "`.`ID_user`" : null) .
		        ($this->conditions ? " WHERE " . $this->conditions : null) .
		        $this->_makeInclude() .
		        ($this->group ? " GROUP BY " . $this->group : null) .
		        ($this->order ? " ORDER BY " . $this->order : null) .
		        (isset($this->limit) ? " LIMIT " . $this->offset . ", " . $this->limit : null);
	}
	/**
	 * Метод формирования селекта из комбайна
	 *
	 */
	private function _makeCombineSelect() {
		$retStr = '';
		if (isset($this->combine)) foreach ($this->combine as $table => $fields) {
			$arrFields = explode(',', $fields);
			foreach ($arrFields as $field) {
				$retStr .= "`" . $table . '`.' . $field . ', ';
			}

		}
		else return $retStr;
		return substr($retStr, 0, strlen($retStr) - 2);

	}
	/**
	 * Метод формирования джоинов при инклуде
	 *
	 */
	private function _makeInclude() {
		$retStr = '';
		if (isset($this->include)) foreach ($this->include as $joinTableName) {
			$retStr .= $this->_makeOneInclude($joinTableName);
		}
		return $retStr;
	}
	/**
	 * Метод формирования джоинов при инклуде
	 *
	 */
	private function _makeOneInclude($joinTableName) {
		foreach (get_object_vars($this) as $attrName => $attrValue) {
			if (is_a($this->$attrName, 'HasMany')) {
		  	$objHasMany = $this->$attrName;
		  	if ($objHasMany->tableName == $joinTableName) {
			  	if (!isset($objHasMany->throw))
			  	  return " LEFT JOIN `" . $objHasMany->tableName . "` " .
			  	  "ON `" . $objHasMany->tableName . "`.`" . $objHasMany->fkName . "` = `" . $this->tableName . "`.`" . $objHasMany->fkName . "`";
			  	else
			  	  return " LEFT JOIN `" . $objHasMany->throw . "` " .
			  	  "ON `" . $objHasMany->throw . "`.`" . $this->pkName . "` = `" . $this->tableName . "`.`" . $this->pkName . "`" .
			  	  " LEFT JOIN `" . $objHasMany->tableName . "` " .
			  	  "ON `" . $objHasMany->tableName . "`.`" . $objHasMany->fkName . "` = `" . $objHasMany->throw . "`.`" . $objHasMany->fkName . "`" ;
		  	}
			}
	  }
	}
	/**
	 * Метод устанавливает свойство (для охранения)
	 *
	 * @param string $settingName
	 * @param mixed $settingValue
	 */
	public function setSaveSetting($settingName, $settingValue) {
		switch ($settingName) {
			case 'saveInclude':
				$this->setSaveInclude($settingValue);
				break;
			default:
				throw new Exception('You are trying to use invalid configuration ('. $settingName . '). To assume the settings are: "saveInclude".');
				break;
		}
	}
	/**
	 * Метод устанавливает свойство (поисковое)
	 *
	 * @param string $settingName
	 * @param mixed $settingValue
	 */
	public function setSetting($settingName, $settingValue) {
		switch ($settingName) {
			case 'select':
				$this->setSelect($settingValue);
				break;
			case 'conditions':
				$this->setConditions($settingValue);
				break;
			case 'order':
				$this->setOrder($settingValue);
				break;
		  case 'group':
				$this->setGroup($settingValue);
				break;
			case 'limit':
				$this->setLimit($settingValue);
				break;
			case 'offset':
				$this->setOffset($settingValue);
				break;
			case 'include':
				$this->setInclude($settingValue);
				break;
			case 'includeList':
			  $this->setIncludeList($settingValue);
				break;
			case 'combine':
				$this->setCombine($settingValue);
				break;
			case 'joins':
				$this->setJoins($settingValue);
				break;
			case 'author':
				$this->getAuthor = true;
				break;
			default:
				throw new Exception('You are trying to use invalid configuration ('. $settingName . '). The assume settings are: "select", "conditions", "joins", "include", "includeList", "order", "limit", "offset"');
				break;
		}
	}

	/**
	 * Устанавливает свойство поиска SELECT
	 *
	 * @param string $select
	 */
	private function setSelect($select) {
		$this->select = $select;
	}
	/**
	 * Устанавливает свойство поиска WHERE
	 *
	 * @param string $conditions
	 */
	private function setConditions($conditions) {
		$this->conditions = $conditions;
	}
	/**
	 * Устанавливает свойство поиска ORDER
	 *
	 * @param string $order
	 */
	private function setOrder($order) {
		$this->order = $order;
	}
	/**
	 * Устанавливает свойство поиска GROUP
	 *
	 * @param string $group
	 */
	private function setGroup($group) {
		$this->group = $group;
	}
	/**
	 * Устанавливает ограничение на количество элементов в поиске
	 *
	 * @param string $limit
	 */
	private function setLimit($limit) {
		$this->limit = intval($limit);
	}
	/**
	 * Устанавливает начальную позицию поиска
	 *
	 * @param string $offset
	 */
	private function setOffset($offset) {
		$this->offset = intval($offset);
	}
	/**
	 * Устанавливает какие связи нужно задействовать в запросе дополнительно
	 *
	 * @param string $include
	 */
	private function setInclude($include) {
		$this->include = $include;
	}
	/**
	 * Устанавливает какие связи нужно задействовать после запроса дополнительно
	 *
	 * @param string $includeList
	 */
	private function setIncludeList($includeList) {
	  $this->includeList = $includeList;
	}
	/**
	 * Устанавливает какбразом нужно скомбинировать результаты
	 *
	 * @param string $combine
	 */
	private function setCombine($combine) {
		$this->combine = $combine;
	}
	/**
	 * Устанавливает какие таблицы нужно соединить
	 *
	 * @param string $join
	 */
	private function setJoins($join) {
		$this->joins = $join;
	}
	/**
	 * Устанавливает какие коллекции нужно сохранить
	 *
	 * @param string $arrSaveInclude
	 */
	private function setSaveInclude($arrSaveInclude) {
		$this->arrSaveInclude = $arrSaveInclude;
	}
	/**
	 * Устанавливает какую коллекцию также надо сохранить
	 *
	 * @param mixed $saveInclude
	 */
	public function addSaveInclude($saveInclude) {
		if (is_string($saveInclude))
		  $this->arrSaveInclude[] = $saveInclude;
		elseif (is_array($saveInclude)) {
		  $arrSettings = &current($saveInclude);
			$this->arrSaveInclude[] = key($saveInclude);
			$this->arrSaveIncludeSettings[key($saveInclude)] = $arrSettings;
		}

	}

	/**
	 * Метод осуществляет разбор условий поиска
	 *
	 * @param mixed $conditions
	 * @return string
	 */
	private function _parseConditions($conditions) {
		if (is_string($conditions)) // передана просто строка (простое условие поиска)
		  return $conditions; // возвращаем в чистом виде
		elseif (is_array($conditions)) { // используется сложносоставное условие поиска
			if (isset($conditions[0]) && is_string($conditions[0])) { // Если первый параметр строковый (используется вставка в основноу строку доп. параметров)
				return _parseConditionsByCType($conditions);
			}
			else {
				$this->_parseConditionsByArray($conditions);
			}
		}
		else
		  throw new Exception('Is incorrect property "conditions" - are allowed only strings and arrays.');

	}
	/**
	 * Разбор условий поиска, если он задан строкой
	 *
	 * @param string $conditions
	 * @return string
	 */
	private  function _parseConditionsByCType($conditions) {
		$generalString = $conditions[0];
		$sizeConditions = sizeof($conditions);
		$retStr = '';
		for ($i = 1, $searchPositionStart = 0; $i < $sizeConditions; $i++) {
		  $searchPosition = strpos($generalString, '?', $searchPositionStart);
		  if ($searchPosition === false)
		    break;
		  if ($searchPosition == 0 || $generalString{($searchPosition -1)} != '\\' ) {
		  	$retStr .= substr($generalString, $searchPositionStart, $searchPosition - $searchPositionStart) . self::getSafeParam($conditions[$i]);
		  	$searchPositionStart = $searchPosition + 1;
		  }
		  else {
		    $retStr .= substr($generalString, $searchPositionStart, $searchPosition - $searchPositionStart + 1);
		    $i--;
		    $searchPositionStart = $searchPosition + 1;
		  }
		}
		$retStr .= substr($generalString, $searchPositionStart);
		return $retStr;
	}
	/**
	 * Разбирает условия поиска, если они заданы массивом
	 *
	 * @param array $conditions
	 * @return string
	 */
	private  function _parseConditionsByArray($conditions) {
		$retStr = '';
		$i = 0;
		foreach ($conditions as $key => $val) {
  		if ( $i > 0)
  		  $retStr . ' AND ';
  		$retStr .= "`" . $key . "` = '" . self::getSafeParam($val) . "'";
  		$i++;
  	}
  	return $retStr;
	}
	/**
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 */
	public static function getSafeParam($unSafeParam) {
		return $unSafeParam;
	}
	/**
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	 */
	public static function getSafeField($unSafeField) {
		return $unSafeField;
	}


	protected function beforeValidation() {}
	protected function beforeValidationOnCreate() {}
	protected function beforeValidationOnUpdate() {}
	protected function beforeValidationOnPartialUpdate() {}
	public function validation($validationType, $arrAdditionalSettings = array()) {
	  // Проверка установки флажка
		if ($this->validatesAcceptanceOf) foreach ($this->validatesAcceptanceOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
			  if ($validationType == 'save') $this->validateAcceptanceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) { $validationType; $this->validateAcceptanceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));}

		// Проверка на привязку к объекту
		if ($this->validatesAssociated) foreach ($this->validatesAssociated as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateAssociated($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateAssociated($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

		// Проверка на подтверждение данного поля
		if ($this->validatesConfirmationOf) foreach ($this->validatesConfirmationOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateConfirmationOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateConfirmationOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

		// Проверка на соответствие регулярному выражению
		if ($this->validatesFormatOf) foreach ($this->validatesFormatOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateFormatOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateFormatOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

		// Проверка длины поля
		if ($this->validatesLengthOf) foreach ($this->validatesLengthOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateLengthOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateLengthOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

		// Проверка на числовой ввод
		if ($this->validatesNumericalityOf) foreach ($this->validatesNumericalityOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateNumericalityOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateNumericalityOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

			// Проверка заполненности поля
		if ($this->validatesPresenceOf) foreach ($this->validatesPresenceOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validatePresenceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validatePresenceOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

			// Проверка на уникальность значения
		if ($this->validatesUniquenessOf) foreach ($this->validatesUniquenessOf as $fieldToValidate => $arrToValidate)
			if ( !isset($arrToValidate['on'])) {
				if ($validationType == 'save') $this->validateUniquenessOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));
			}
			elseif ($validationType == $arrToValidate['on']) $this->validateUniquenessOf($fieldToValidate, array_merge($arrToValidate, $arrAdditionalSettings));

	}
	protected function afterValidation() {}
	protected function afterValidationOnCreate() {}
	protected function afterValidationOnUpdate() {}
	protected function beforeSave() {}
	protected function beforeCreate() {}
	protected function beforeUpdate() {}
	protected function beforePartialUpdate() {}
	protected function afterCreate() {}
	protected function afterUpdate() {}
	protected function afterSave() {}
	protected function beforeDestroy() {}
	protected function afterDestroy() {}

	protected function beforeDelete() {}
	protected function afterDelete() {}

	protected function operationInsert() {}
	protected function operationUpdate() {}
	protected function operationDestroy() {}

	public function errorValidated($fieldToValidate, &$arrToValidate, $alternativeMessage = '') {
		if (isset($arrToValidate['if'])) eval($arrToValidate['if']);
		if (isset($arrToValidate['rowException']) && $arrToValidate['rowException'])
		  Exception_ActiveRecord::addRowException(isset($arrToValidate['errorOf']) ? $arrToValidate['errorOf'] : get_class($this), $alternativeMessage, isset($arrToValidate['complex']) ? $arrToValidate['complex'] : null);
		else{
		  Exception_ActiveRecord::addObjectException(isset($arrToValidate['errorOf']) ? $arrToValidate['errorOf'] : get_class($this), $fieldToValidate, $alternativeMessage, isset($arrToValidate['complex']) ? $arrToValidate['complex'] : null);
		}
		return false;
	}

	// проверка установки флажка
	public function validateAcceptanceOf($fieldToValidate, &$arrToValidate = null) {

		if (!isset($arrToValidate['accept']) ) $arrToValidate['accept'] = 1;

		if (isset($this->tableFields[$fieldToValidate]) && !$arrToValidate['accept'] || !isset($this->tableFields[$fieldToValidate]) && $arrToValidate['accept'])
			return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : (!$arrToValidate['accept'] ? 'не ' : null) . 'должно быть отмечено');
		return true;
	}
	// проверка связанных объектов
	public function validateAssociated($fieldToValidate, $arrToValidate = null) {
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) )  return $this->errorValidated($fieldToValidate, $arrToValidate, ($arrToValidate['message'] ? $arrToValidate['message'] : 'поле не может быть пустым'));
    elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		// если задано регулярное выражение
		if (isset($arrToValidate['table'])) {
			if ($this->arrBelongsTo) foreach ($this->arrBelongsTo as $belongsTo) {
				if ($belongsTo->tableName == $arrToValidate['table']) {
		  		$stmt = "SELECT `" . $belongsTo->fkName . "` FROM `" . $arrToValidate['table'] . "` WHERE `" . $belongsTo->fkName . "` = " . intval($this->tableFields[$fieldToValidate]);
		  		$objDB = Service_Database::instance();//Получаем подключение к БД
		  		if (!$objDB->isRecordExist($stmt))
		  		  return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'некорректно');
		  		break;
		  	}
		  }
		}
		elseif (isset($arrToValidate['class'])) {
			$s = "\$obj = new " . $arrToValidate['class'] . "();";
			eval($s);
			$stmt = "SELECT `" . $obj->pkName . "` FROM `" . $obj->tableName . "` WHERE `" . $obj->pkName . "` = " . intval($this->tableFields[$fieldToValidate]);
  		$objDB = Service_Database::instance();//Получаем подключение к БД
  		if (!$objDB->isRecordExist($stmt))
  		  return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'некорректно');
		}
		return true;
	}
	// проверяет совпадение с полем двойником
	public function validateConfirmationOf($fieldToValidate, $arrToValidate = null) {
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, ($arrToValidate['message'] ? $arrToValidate['message'] : 'поле не может быть пустым'));
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		if ($this->tableFields[$fieldToValidate] != $this->tableFields[$fieldToValidate . '_confirmation'])
			return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'поле не совпадает с его подтверждением');
	  return true;
	}
	// проверяет остутсвие значения свойств в наборе
	public function validateExclusionOf($fieldToValidate, $arrToValidate = null) {
		return true;
	}
	// проверяет на соответствие регулярному выражению
	public function validateFormatOf($fieldToValidate, $arrToValidate = null) {
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, ($arrToValidate['message'] ? $arrToValidate['message'] : 'поле не может быть пустым'));
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		// если задано регулярное выражение
		if (isset($arrToValidate['with'])) {
		  if (!preg_match($arrToValidate['with'], $this->tableFields[$fieldToValidate]))
		    return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'поле заполнено неверно');
		}
		elseif (isset($arrToValidate['standard'])) {
			switch ($arrToValidate['standard']) {
				case 'humanDate':
					if (!Service_Date::isHumanDateCorrect($this->tableFields[$fieldToValidate]) || !Service_Date::humanDateToUnix($this->tableFields[$fieldToValidate]))
					  return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'неверно указана дата');
					break;
				case 'dbDate':
					if (!Service_Date::isDBDateCorrect($this->tableFields[$fieldToValidate]))
					  return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'неверно указана дата');
					break;
			}
		}
		return true;
	}
	// проверяет значения свойств на присутствие в наборе
	public function validateInclusionOf($fieldToValidate, $arrToValidate = null) {
		return true;
	}
	// проверяет длину значения свойства
	public function validateLengthOf($fieldToValidate, $arrToValidate = null) {
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, ($arrToValidate['message'] ? $arrToValidate['message'] : 'поле не может быть пустым'));
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		//$fieldLength = strlen($this->tableFields[$fieldToValidate]);
		$fieldLength = Service_Function::utf8_strlen($this->tableFields[$fieldToValidate]);

		// если задано точное совпадение
		if (isset($arrToValidate['is']))
		  if ($fieldLength != $arrToValidate['is'])
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['wrongLength']) ? $arrToValidate['wrongLength'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : 'неправильная длина поля (должно быть ' . $arrToValidate['is'] . ' символов)');

		// если задано минимальное ограничение
		if (isset($arrToValidate['minimum']))
		  if ($fieldLength < $arrToValidate['minimum']) {
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooShort']) ? $arrToValidate['tooShort'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : 'слишком мало символов (должно быть не менее ' . $arrToValidate['minimum'] . ' символов)');
		  }
		// если задано максимальное ограничение
		if (isset($arrToValidate['maximum']))
		  if ($fieldLength > $arrToValidate['maximum'])
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooLong']) ? $arrToValidate['tooLong'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : 'слишком много символов (должно быть не более ' . $arrToValidate['maximum'] . ' символов)');
		return true;

	}
	// проверяет что свойство содержит правильное число
	public function validateNumericalityOf($fieldToValidate, $arrToValidate = null) {
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, ($arrToValidate['message'] ? $arrToValidate['message'] : 'поле не может быть пустым'));
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;

		if (!is_numeric($this->tableFields[$fieldToValidate]) || isset($arrToValidate['onlyInteger']) && !is_integer($this->tableFields[$fieldToValidate]))
	    return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'должно быть числом');

		// если задано минимальное ограничение
		if (isset($arrToValidate['minimum']))
		  if ($this->tableFields[$fieldToValidate] < $arrToValidate['minimum']) {
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooShort']) ? $arrToValidate['tooShort'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : 'слишком малое значение (должно быть не менее ' . $arrToValidate['minimum'] . ')');
		  }
		// если задано максимальное ограничение
		if (isset($arrToValidate['maximum']))
		  if ($this->tableFields[$fieldToValidate] > $arrToValidate['maximum'])
		  	return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['tooLong']) ? $arrToValidate['tooLong'] : isset($arrToValidate['message']) ? $arrToValidate['message'] : 'слишком большое значение (должно быть не более ' . $arrToValidate['maximum'] . ')');
	  return true;
	}
	// Проверяет, что свойство не имеет пустого значения
	public function validatePresenceOf($fieldToValidate, $arrToValidate = null) {
    if (!isset($this->tableFields[$fieldToValidate]) || $this->tableFields[$fieldToValidate] === '' || !$this->tableFields[$fieldToValidate] && $this->tableFields[$fieldToValidate] !== 0) // если такого поля нет
	    return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'поле не может быть пустым');
	  return true;
	}
	// Проверяет свойство на уникальность
	public function validateUniquenessOf($fieldToValidate, $arrToValidate = null) {
		// если такого поля нет или оно содержит nool, которое не разрешено
		if (empty($this->tableFields[$fieldToValidate]) && ( !isset($arrToValidate['allowNull']) || !$arrToValidate['allowNull'] ) ) return $this->errorValidated($fieldToValidate, $arrToValidate, ($arrToValidate['message'] ? $arrToValidate['message'] : 'поле не может быть пустым'));
		elseif (empty($this->tableFields[$fieldToValidate]) && ( isset($arrToValidate['allowNull']) && $arrToValidate['allowNull'] ) ) return true;


		$stmt = "SELECT `" . $this->pkName . "` FROM `" . $this->tableName . "` WHERE `" . $fieldToValidate . "`='" . $this->tableFields[$fieldToValidate] . "'";
		if ($this->id) $stmt .= " AND `" . $this->pkName . "` != '" . $this->id . "'";
		$objDB = Service_Database::instance();//Получаем подключение к БД
	  if ($objDB->isRecordExist($stmt))
	    return $this->errorValidated($fieldToValidate, $arrToValidate, isset($arrToValidate['message']) ? $arrToValidate['message'] : 'такое значение уже используется');
	  return  true;
	}
	/**
	 * Добавляет к массиву полей дополнительные поля
	 *
	 * @param array $arr
	 */
	public function merge($arr) {
		$this->tableFields = array_merge($this->tableFields, $arr);
	}

	/**
	 * Удаляет элемент по его идентификатору
	 *
	 * @param integer $id
	 * @return integer
	 */
  public function delete($id = null) {
  	if (!$id) {
  	  if (isset($this->id)) $id = $this->id;
  	  else return false;
  	}
  	if (isset($this->arrSettings['tags'])) {
  	  $objTags = new Tag_Essence($this->id, $this->essenceType);
  	  $objTags->deleteAllFromEssence();
  	}
  	if (isset($this->arrSettings['image'])) {
  	  if (!isset($this->tableFields['pic_folder'])) $this->_findOne($id);
  	  Image::deleteAvatarOfElement($this->tableFields['pic_folder'], $this->tableFields['pic_file']);
  	}
  	if (isset($this->arrSettings['ico'])) {
  	  if (!isset($this->tableFields['ico_folder'])) $this->_findOne($id);
  	  Image::deleteAvatarOfElement($this->tableFields['ico_folder'], $this->tableFields['ico_file']);
  	}
  	$objDB = Service_Database::instance();//Получаем подключение к БД
  	$stmt = "DELETE FROM `" . $this->tableName . "` WHERE `" . $this->pkName . "` = '" . intval($id) . "'";
  	return $objDB->query($stmt);
  }

	/**
	 * Псевдоудаление - устанавливает поле is_deleted в бд в значение 1
	 *
	 * @param integer $id - id элемента, может быть задан при создании объекта
	 * @return integer
	 */
  public function pseudoDelete($id = null) {
  	if (!$id) {
  	  if (isset($this->id)) $id = $this->id;
  	  else return false;
  	}
  	$objDB = Service_Database::instance(); // Получаем подключение к БД
  	return $objDB->update($this->tableName, array($this->tableDeleteFlag => 1), array($this->pkName => intval($id)));
  }

	/**
	 * Редактирует поля по переданному массиву, id и все поля должны быть заданы при создании объекта
	 *
	 * @param array $arrUpdate - массив с имена полей, которые нужно изменить
	 * @param integer $id - id элемента, может быть задан при создании объекта
	 * @return integer
	 */
  public function partialUpdate($arrFields, $id = null) {
  	if (!$id) {
  	  if (isset($this->id)) $id = $this->id;
  	  else return false;
  	}
    $this->beforeValidation();
		if ($this->id) $this->beforeValidationOnPartialUpdate();
		$this->validation('partialUpdate', array());

  	if (!Exception_ActiveRecord::isExceptionOfModelExists(get_class($this))) {
	  	$this->beforePartialUpdate();
	  	foreach ($arrFields as $field) $arrUpdate[$field] = $this->tableFields[$field];
	  	$objDB = Service_Database::instance(); // Получаем подключение к БД

	  	$objDB->update($this->tableName, $arrUpdate, array($this->pkName => intval($id))); // редактируем поля
	  	return true; // ставим true, чтобы без изменений не возвращался 0
  	}
  	else return false;
  }
  /*
  private function changValidationOn($arrFields, $on) {
  	$arrFieldsSize = sizeof($arrFields);
  	if ($arrFieldsSize > 0) {
	  	foreach ($arrFields as $field) {
		 		// Проверка установки флажка
				if ($this->validatesAcceptanceOf) foreach ($this->validatesAcceptanceOf as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesAcceptanceOf[$fieldToValidate]['on'] = $on;

				// Проверка на привязку к объекту
				if ($this->validatesAssociated) foreach ($this->validatesAssociated as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesAssociated[$fieldToValidate]['on'] = $on;

				// Проверка на подтверждение данного поля
				if ($this->validatesConfirmationOf) foreach ($this->validatesConfirmationOf as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesConfirmationOf[$fieldToValidate]['on'] = $on;

				// Проверка на соответствие регулярному выражению
				if ($this->validatesFormatOf) foreach ($this->validatesFormatOf as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesFormatOf[$fieldToValidate]['on'] = $on;

				// Проверка длины поля
				if ($this->validatesLengthOf) foreach ($this->validatesLengthOf as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesLengthOf[$fieldToValidate]['on'] = $on;

				// Проверка на числовой ввод
				if ($this->validatesNumericalityOf) foreach ($this->validatesNumericalityOf as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesNumericalityOf[$fieldToValidate]['on'] = $on;

					// Проверка заполненности поля
				if ($this->validatesPresenceOf) foreach ($this->validatesPresenceOf as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesPresenceOf[$fieldToValidate]['on'] = $on;

					// Проверка на уникальность значения
				if ($this->validatesUniquenessOf) foreach ($this->validatesUniquenessOf as $fieldToValidate => $arrToValidate)
					if ($field == $fieldToValidate) $this->validatesUniquenessOf[$fieldToValidate]['on'] = $on;
	  	}
  	}
  }
  */

  /**
   * Удаляет список элементов
   *
   * @param string $conditions
   * @return integer
   */
	public function delete_all($conditions, $ignoreLinks = false) {
  	$objDB = Service_Database::instance();//Получаем подключение к БД
		if (isset($this->arrSettings['image']) || isset($this->arrSettings['ico']) || isset($this->arrSettings['tags']) || $this->arrHasMany) {
  		$stmt = "SELECT `" . $this->pkName . "`" . (isset($this->arrSettings['image']) ? ", `pic_folder`, `pic_file`" : null) . (isset($this->arrSettings['ico']) ? ", `ico_folder`, `ico_file`" : null) . " FROM `" . $this->tableName . "` WHERE " . $conditions;
  		$arrElements = $objDB->select($stmt);
  		//Удаляем изображения при необходимости
  		if (isset($this->arrSettings['image']) && $arrElements) foreach ($arrElements as $element) {
  			Image::deleteAvatarOfElement($element['pic_folder'], $element['pic_file']);
  		}
  		//Удаляем изображения при необходимости
  		if (isset($this->arrSettings['ico']) && $arrElements) foreach ($arrElements as $element) {
  			Image::deleteAvatarOfElement($element['ico_folder'], $element['ico_file']);
  		}
  		if (isset($this->arrSettings['tags']) && $arrElements) {
  			$arrIds = array();
  			foreach ($arrElements as $element) {
  				$arrIds[] = $element[$this->pkName];
  			}
  			$objTags = new Tags_Tags();
        $objTags->deleteTagsOfEssenceArray($arrIds);
  		}
  	}
		if ($this->arrHasMany) foreach ($this->arrHasMany as $hasMany) {
  		if ($hasMany->tableName != $obj->tableName) $hasMany = null;
  		else break;
  	}
  	if (!$ignoreLinks) {
  		if ($this->arrHasMany) foreach ($this->arrHasMany as $hasMany) {
  			if ($hasMany->throw) {
  				$stmt = "DELETE FROM  `" . $hasMany->throw . "` WHERE `" . $this->pkName . "` = `" . $this->id . "` AND `" . $hasMany->fkName . "` IN (" . join(', ', $arrDeleteIdsList) . ")";
  	    	$objDB->query();
  			}
  			else {
  				if ($hasMany->dependment) {
						if (!isset($arrIds))
						  $arrIds = Service_ArrayConverter::getSimpleArray($arrElements, $this->pkName);
  					if ($hasMany->dependment == 'destroy') {
  						//$stmt = "SELECT `" . $hasMany->fkName . "` FROM `" . $hasMany->tableName . "` WHERE `" . $this->pkName . "` IN (" . join($arrIds) . ")";
  						//$arrHasManyIds = $objDB->selectSimleArray();
  						if ($arrHasManyIds) {
  							$s = "\$obj = new " . $hasMany->className . "();";
  							eval($s);
  							$obj->delete_all(" `" . $this->pkName . "` IN (" . join($arrIds) . ")");
  						}

  					}
  					elseif ($hasMany->dependment == 'nullify') {
  						$stmt = "UPDATE `" . $hasMany->tableName . "` SET `" . $this->id . "` = null WHERE `" . $this->pkName . "` IN (" . join($arrIds) . ")";
  	    			$objDB->query();
  					}
  				}
  			}
  		}
  	}

  	$stmt = "DELETE FROM `" . $this->tableName . "` WHERE " . $conditions;
  	return $objDB->query($stmt);
	}

	/**
	 * Подсчитывает количество элементов согласно условию
	 *
	 * @param string $conditions
	 */
	public function count($conditions) {
		$objDB = Service_Database::instance();//Получаем подключение к БД
		$stmt = "SELECT COUNT(`" . $this->pkName . "`) FROM `" . $this->tableName . "` WHERE " . $conditions;
		return $objDB->selectSimpleField($stmt);
	}

	/**
	 * Метод возвращает подстроку для вставки LIMIT
	 *
	 * @param int $limit
	 * @param int $offset
	 * @return string
	 */
	public static function getLimitStr($limit = 1, $offset = 0) {
		return " LIMIT " . intval($offset) . ", " . intval($this->limit);

	}



/***********************Обработка добавления в избраныне**************************/

  /**
   * Метод добавляет элемент в список избранного некоторого пользователя
   *
   * @param integer $userId
   * @param string $favoritesTableName - название таблицы, в которой хранятся элементы favorite
   * @param string $favoritesPkName - название первичного ключа, которому соответствует ID сущности добавляемой в избранное
   * @return bool
   */
  public function addEssenceAsFavorite($userId, $favoritesTableName = null, $favoritesPkName = null) {
    if (!$userId) return false;
    if (!$favoritesTableName) $favoritesTableName = 'favorite_' . $this->tableName;
    if (!$favoritesPkName) $favoritesPkName = $this->pkName;
		// Проверяем, есть ли уже запись в избранном
		if (!self::isFavorite($userId, $favoritesTableName)) { // Если записи в избранном еще нет
      $essenceId = $this->id;
		  $objDB = Service_Database::instance();
		  $stmt = 'INSERT INTO `' . $favoritesTableName . '`
							 (`ID_user`, `' . $favoritesPkName . '`)
							 VALUES ("' . $userId . '", "' . $essenceId . '")';
			$objDB->query($stmt);
			self::incrementFavoritesAmount();
			//Rating::reCalculateForEssence($this->tableName, $essenceId, 'favorites_amount');
		}
	  return true;
  }

  /**
   * Метод удаления элемента из избранного
   *
   * @param integer $userId
   * @param string $favoritesTableName - название таблицы, в которой хранятся элементы favorite
   * @param string $favoritesPkName - название первичного ключа, которому соответствует ID сущности добавляемой в избранное
   * @return bool
   */
  public function removeEssenceFromFavorites($userId, $favoritesTableName = null, $favoritesPkName = null) {
    if (!$userId) return false;
    if (!$favoritesTableName) $favoritesTableName = 'favorite_' . $this->tableName;
    if (!$favoritesPkName) $favoritesPkName = $this->pkName;
    $essenceId = $this->id;
		$objDB = Service_Database::instance();
		$stmt = "DELETE
						 FROM `" . $favoritesTableName . "`
						 WHERE ID_user = '" . $userId . "' AND `" . $favoritesPkName . "` = '" . $essenceId . "'";
		$deleteResult = $objDB->query($stmt);
		if ($deleteResult) { // Если мы действительно что-то удалили
		  self::decrementFavoritesAmount();
		  //Rating::reCalculateForEssence($this->tableName, $essenceId, 'favorites_amount');
		}
		return $deleteResult;

  }

  /**
   * Метод увеличивает счетчик добавивших в избранное на 1
   * @param string $favoritesTableName - название таблицы, в которой хранятся элементы favorite
   * @param string $favoritesPkName - название первичного ключа, которому соответствует ID сущности добавляемой в избранное
   * @return integer
   */
	public function incrementFavoritesAmount($favoritesTableName = null, $favoritesPkName = null) {
	  if (!$favoritesTableName) $favoritesTableName = $this->tableName;
	  if (!$favoritesPkName) $favoritesPkName = $this->pkName;
	  $essenceId = $this->id;
		$objDB = Service_Database::instance();
		$stmt = "UPDATE `" . $favoritesTableName . "`
	           SET `favorites_amount`=(`favorites_amount` + 1)
	           WHERE `" . $favoritesPkName . "` = " . $essenceId;
	  return $objDB->query($stmt);
	}

  /**
   * Метод уменьшает счетчик добавивших элемент в избранное на 1
   * @param string $favoritesTableName - название таблицы, в которой хранятся элементы favorite
   * @param string $favoritesPkName - название первичного ключа, которому соответствует ID сущности добавляемой в избранное
   * @return integer
   */
	public function decrementFavoritesAmount($favoritesTableName = null, $favoritesPkName = null) {
	  if (!$favoritesTableName) $favoritesTableName = $this->tableName;
	  if (!$favoritesPkName) $favoritesPkName = $this->pkName;
	  $essenceId = $this->id;
		$objDB = Service_Database::instance();
		$stmt = "UPDATE `" . $favoritesTableName . "`
	           SET `favorites_amount`=(`favorites_amount` - 1)
	           WHERE `" . $favoritesPkName . "` = " . $essenceId;
	  return $objDB->query($stmt);
	}

	/**
	 * Метод получает информацию о том добавлена ли сущность в избранное
   * @param integer $userId
   * @param string $favoritesTableName - название таблицы, в которой хранятся элементы favorite
   * @param string $favoritesPkName - название первичного ключа, которому соответствует ID сущности добавляемой в избранное
   * @return bool
	 */
	public function isFavorite($userId, $favoritesTableName = null, $favoritesPkName = null) {
    if (!$userId) return false;
	  if (!$favoritesTableName) $favoritesTableName = 'favorite_' . $this->tableName;
	  if (!$favoritesPkName) $favoritesPkName = $this->pkName;
    $essenceId = $this->id;
		$objDB = Service_Database::instance();
		$stmt = 'SELECT `' . $favoritesTableName . '`.`' . $favoritesPkName . '`
						 FROM `' . $favoritesTableName . '`
						 WHERE ID_user = "' . $userId . '" AND `' . $favoritesPkName . '` = "' . $essenceId . '"';
		return $objDB->isRecordExist($stmt);
	}

	/**
	 * обновляем одно поле у нескольких записей
	 *
	 * @param string $tableName - имя таблицы
	 * @param string $fieldUpdate - имя поля для обновления
	 * @param string $fieldID - имя поля с идентификатором
	 * @param array $arrUpdate - массив с даанвми для обновления Напрмер: array(58=>array(0=>2, 1=>5), 36=>array(0=>4, 1=>9))
	 * @return bool
	 */
	public static function updateFewRecord($tableName, $fieldUpdate, $fieldID, $arrUpdate) {
		if (empty($arrUpdate))
			return false;
		$objDB = Service_Database::instance();
		$stmtSet = 'SET `' . $tableName . '`.`' . $fieldUpdate . '` = CASE `' . $tableName . '`.`' . $fieldID . '`';
		foreach ($arrUpdate as $update) $stmtSet .= ' WHEN "' . $update[0] . '" THEN "' . $update[1] . '" ';
		$stmtSet .= 'END';
		$stmt = '
		UPDATE `' . $tableName . '`' . $stmtSet;
		return $objDB->query($stmt);
	}

	/**
	 * Метод проверяет, какие элементы из списка находятся в избранном у пользователя
	 *
	 * @param array $arrItems
	 * @param integer $userId
	 * @param string $favoritesTableName
	 * @param string $favoritesPkName
	 * @return array
	 */
  public function setFavoritesFlag(&$arrItems, $userId, $favoritesTableName = null, $favoritesPkName = null) {
    if (!$userId) return false;
    if (empty($arrItems))  return false;
  	if (!$favoritesTableName) $favoritesTableName = 'favorite_' . $this->tableName;
  	if (!$favoritesPkName) $favoritesPkName = $this->pkName;
    $essenceId = $this->id;
  	$objDB = Service_Database::instance();
  	$stmt = 'SELECT `' . $favoritesTableName . '`.`' . $favoritesPkName . '`
  					 FROM `' . $favoritesTableName . '`
  					 WHERE ID_user = "' . $userId . '" AND `' . $favoritesPkName . '` IN (' . Service_Function::intEscapeJoinOfPart(',', $arrItems, $this->pkName) . ')';
  	$arrFavoriteItemIds = $objDB->select($stmt);

  	$arrItems = Service_ArrayConverter::combineArraysAddOneField($arrItems, $arrFavoriteItemIds, $favoritesPkName, $this->pkName, 'is_favorite', $favoritesPkName);
  	return $arrItems;
  }


  /**
   * Возвращает кол-во типовых страниц подходящих под условия
   *
   * @param $condition - Условия
   * @param $join - Подключаемые таблицы
   * @return
   */
  public function getElementsAmount($condition = null, $join = null) {
  	$arrCount = $this->find('all', array('select' => 'COUNT(`' . $this->tableName . '`.`' . $this->pkName . '`) AS count',
	  'joins' => $join,
	  'conditions' => $condition));
	  return $arrCount[0]['count'];
  }
  
  /**
   * Метод обновляет одно конкретное поле у таблицы
   *
   * @param string $fieldName - название поля в таблице
   * @param string $fieldValue - новое значение
   * @return bool
   */
  public function updateField($fieldName, $fieldValue) {
    if (!$this->id) return false;
		$objDB = Service_Database::instance();
		$stmt = "UPDATE `" . $this->tableName . "` 
		SET `" . $this->tableName . "`.`" . $fieldName . "` = '" . Service_Function::absolutelyEscapeString($fieldValue) . "'
		WHERE `" . $this->tableName . "`.`" . $this->pkName . "` = '" . intval($this->id) . "'";
		return $objDB->query($stmt);
  }
  
  /**
   * Метод возвращает часть запроса в которой содержится условия поиска за период времени
   *
   * @param string $periodType
   * @return string
   */
  public static function getPeriodStmtWhere($periodType = '', $periodFieldName = '`created_at`') {
    switch ($periodType) {
      case 'year':
        return 'AND YEAR(' . $periodFieldName . ') = YEAR(CURRENT_DATE)';
        break;
      case 'month':
        return 'AND MONTH(' . $periodFieldName . ') = MONTH(CURRENT_DATE)';
        break;
      case 'week':
        return 'AND ' . $periodFieldName . ' > (DATE_SUB(NOW(), INTERVAL 7 DAY))';
        break;
      case 'day':
        return 'AND ' . $periodFieldName . ' > (DATE_SUB(NOW(), INTERVAL 1 DAY))';
        break;
      case 'total': default:
        return '';
        break;
	  }
  }
  
  	  

}





/*
создать метод create, который создает и сохраняет данные в БД
создать метод update_attribute и update_attributes и update(id, ...) и update_all(set, where)
создать методы delete_all и destroy
создать методы для агрегатных функций (average, sum, count) [320]
*/




?>